En omfattende guide til tre traverseringsalgoritmer: Dybde-først-søk (DFS) og Bredde-først-søk (BFS). Lær om deres prinsipper, implementering, brukstilfeller og ytelsesegenskaper.
Tre Traverseringsalgoritmer: Dybde-først-søk (DFS) vs. Bredde-først-søk (BFS)
I informatikk er tre traversering (også kjent som tre søk eller trevandring) prosessen med å besøke (undersøke og/eller oppdatere) hver node i en tre datastruktur, nøyaktig én gang. Trær er fundamentale datastrukturer som brukes mye i forskjellige applikasjoner, fra å representere hierarkiske data (som filsystemer eller organisasjonsstrukturer) til å legge til rette for effektive søke- og sorteringsalgoritmer. Å forstå hvordan man traverserer et tre er avgjørende for å jobbe effektivt med dem.
To primære tilnærminger til tretraversering er Dybde-først-søk (DFS) og Bredde-først-søk (BFS). Hver algoritme tilbyr distinkte fordeler og er egnet for forskjellige typer problemer. Denne omfattende guiden vil utforske både DFS og BFS i detalj, og dekke deres prinsipper, implementering, brukstilfeller og ytelsesegenskaper.
ForstĂĄ Tre Datastrukturer
Før vi dykker ned i traverseringsalgoritmene, la oss kort gjennomgå det grunnleggende om tre datastrukturer.
Hva er et Tre?
Et tre er en hierarkisk datastruktur som består av noder forbundet med kanter. Det har en rot node (den øverste noden), og hver node kan ha null eller flere barnenoder. Noder uten barn kalles blad noder. Viktige egenskaper ved et tre inkluderer:
- Rot: Den øverste noden i treet.
- Node: Et element i treet, som inneholder data og potensielt referanser til barnenoder.
- Kant: Koblingen mellom to noder.
- Forelder: En node som har en eller flere barnenoder.
- Barn: En node som er direkte koblet til en annen node (dens forelder) i treet.
- Blad: En node uten barn.
- Subtre: Et tre dannet av en node og alle dens etterkommere.
- Dybde av en node: Antall kanter fra roten til noden.
- Høyde på et tre: Maksimal dybde for en hvilken som helst node i treet.
Typer Trær
Flere varianter av trær eksisterer, hver med spesifikke egenskaper og brukstilfeller. Noen vanlige typer inkluderer:
- Binært Tre: Et tre der hver node har maksimalt to barn, vanligvis referert til som venstre barn og høyre barn.
- Binært Søketre (BST): Et binært tre der verdien av hver node er større enn eller lik verdien av alle noder i dets venstre subtre og mindre enn eller lik verdien av alle noder i dets høyre subtre. Denne egenskapen gir effektiv søking.
- AVL Tre: Et selvbalanserende binært søketre som opprettholder en balansert struktur for å sikre logaritmisk tidskompleksitet for søke-, innsettings- og sletteoperasjoner.
- Rød-Svart Tre: Et annet selvbalanserende binært søketre som bruker fargeegenskaper for å opprettholde balansen.
- N-ært Tre (eller K-ært Tre): Et tre der hver node kan ha maksimalt N barn.
Dybde-Først-Søk (DFS)
Dybde-først-søk (DFS) er en tre traverseringsalgoritme som utforsker så langt som mulig langs hver gren før den går tilbake. Den prioriterer å gå dypt inn i treet før den utforsker søsken. DFS kan implementeres rekursivt eller iterativt ved hjelp av en stakk.DFS Algoritmer
Det er tre vanlige typer DFS traverseringer:
- Inorder Traversal (Venstre-Rot-Høyre): Besøker det venstre subtreet, deretter rot noden, og til slutt det høyre subtreet. Dette brukes ofte for binære søketrær fordi det besøker nodene i sortert rekkefølge.
- Preorder Traversal (Rot-Venstre-Høyre): Besøker rot noden, deretter det venstre subtreet, og til slutt det høyre subtreet. Dette brukes ofte til å lage en kopi av treet.
- Postorder Traversal (Venstre-Høyre-Rot): Besøker det venstre subtreet, deretter det høyre subtreet, og til slutt rot noden. Dette brukes ofte til å slette et tre.
Implementeringseksempler (Python)
Her er Python-eksempler som demonstrerer hver type DFS traversering:
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
# Inorder Traversal (Left-Root-Right)
def inorder_traversal(root):
if root:
inorder_traversal(root.left)
print(root.data, end=" ")
inorder_traversal(root.right)
# Preorder Traversal (Root-Left-Right)
def preorder_traversal(root):
if root:
print(root.data, end=" ")
preorder_traversal(root.left)
preorder_traversal(root.right)
# Postorder Traversal (Left-Right-Root)
def postorder_traversal(root):
if root:
postorder_traversal(root.left)
postorder_traversal(root.right)
print(root.data, end=" ")
# Example Usage
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
print("Inorder traversal:")
inorder_traversal(root) # Output: 4 2 5 1 3
print("\nPreorder traversal:")
preorder_traversal(root) # Output: 1 2 4 5 3
print("\nPostorder traversal:")
postorder_traversal(root) # Output: 4 5 2 3 1
Iterativ DFS (med Stakk)
DFS kan ogsĂĄ implementeres iterativt ved hjelp av en stakk. Her er et eksempel pĂĄ iterativ preorder traversering:
def iterative_preorder(root):
if root is None:
return
stack = [root]
while stack:
node = stack.pop()
print(node.data, end=" ")
# Push right child first so left child is processed first
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
#Example Usage (same tree as before)
print("\nIterative Preorder traversal:")
iterative_preorder(root)
Brukstilfeller av DFS
- Finne en sti mellom to noder: DFS kan effektivt finne en sti i en graf eller et tre. Vurder ruting av datapakker over et nettverk (representert som en graf). DFS kan finne en rute mellom to servere, selv om flere ruter eksisterer.
- Topologisk sortering: DFS brukes i topologisk sortering av rettede asykliske grafer (DAG-er). Se for deg planlegging av oppgaver der noen oppgaver er avhengige av andre. Topologisk sortering ordner oppgavene i en rekkefølge som respekterer disse avhengighetene.
- Oppdage sykluser i en graf: DFS kan oppdage sykluser i en graf. Sykkeldeteksjon er viktig i ressursallokering. Hvis prosess A venter pĂĄ prosess B og prosess B venter pĂĄ prosess A, kan det forĂĄrsake en vranglĂĄs.
- Løse labyrinter: DFS kan brukes til å finne en vei gjennom en labyrint.
- Parsing og evaluering av uttrykk: Kompilatorer bruker DFS-baserte tilnærminger for parsing og evaluering av matematiske uttrykk.
Fordeler og Ulemper med DFS
Fordeler:
- Enkel ĂĄ implementere: Den rekursive implementeringen er ofte veldig kortfattet og lett ĂĄ forstĂĄ.
- Minneeffektiv for visse trær: DFS krever mindre minne enn BFS for dypt nestede trær fordi den bare trenger å lagre nodene på gjeldende sti.
- Kan finne løsninger raskt: Hvis den ønskede løsningen er dypt nede i treet, kan DFS finne den raskere enn BFS.
Ulemper:
- Ikke garantert ĂĄ finne den korteste veien: DFS kan finne en sti, men det er kanskje ikke den korteste veien.
- Potensial for uendelige løkker: Hvis treet ikke er nøye strukturert (f.eks. inneholder sykluser), kan DFS sette seg fast i en uendelig løkke.
- Stakoverflyt: Den rekursive implementeringen kan føre til stakoverflytfeil for veldig dype trær.
Bredde-Først-Søk (BFS)
Bredde-først-søk (BFS) er en tretraverseringsalgoritme som utforsker alle nabo nodene på gjeldende nivå før den går videre til nodene på neste nivå. Den utforsker treet nivå for nivå, og starter fra roten. BFS implementeres vanligvis iterativt ved hjelp av en kø.
BFS Algoritme
- Sett rot noden i køen.
- Mens køen ikke er tom:
- Ta en node ut av køen.
- Besøk noden (f.eks. skriv ut verdien).
- Sett alle barn av noden i køen.
Implementeringseksempel (Python)
from collections import deque
def bfs_traversal(root):
if root is None:
return
queue = deque([root])
while queue:
node = queue.popleft()
print(node.data, end=" ")
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
#Example Usage (same tree as before)
print("BFS traversal:")
bfs_traversal(root) # Output: 1 2 3 4 5
Brukstilfeller av BFS
- Finne den korteste veien: BFS er garantert ĂĄ finne den korteste veien mellom to noder i en uvektet graf. Se for deg sosiale nettverkssider. BFS kan finne den korteste forbindelsen mellom to brukere.
- Graf traversering: BFS kan brukes til ĂĄ traversere en graf.
- Web crawling: Søkemotorer bruker BFS for å crawle nettet og indeksere sider.
- Finne de nærmeste naboene: I geografisk kartlegging kan BFS finne de nærmeste restaurantene, bensinstasjonene eller sykehusene til et gitt sted.
- Flomfyllingsalgoritme: I bildebehandling danner BFS grunnlaget for flomfyllingsalgoritmer (f.eks. "malingsspann"-verktøyet).
Fordeler og Ulemper med BFS
Fordeler:
- Garantert ĂĄ finne den korteste veien: BFS finner alltid den korteste veien i en uvektet graf.
- Egnet for å finne de nærmeste nodene: BFS er effektiv for å finne noder som er nær startnoden.
- Unngår uendelige løkker: Fordi BFS utforsker nivå for nivå, unngår den å sette seg fast i uendelige løkker, selv i grafer med sykluser.
Ulemper:
- Minneintensiv: BFS kan kreve mye minne, spesielt for brede trær, fordi den trenger å lagre alle nodene på gjeldende nivå i køen.
- Kan være tregere enn DFS: Hvis den ønskede løsningen er dypt nede i treet, kan BFS være tregere enn DFS fordi den utforsker alle nodene på hvert nivå før den går dypere.
Sammenligning av DFS og BFS
Her er en tabell som oppsummerer de viktigste forskjellene mellom DFS og BFS:
| Funksjon | Dybde-Først-Søk (DFS) | Bredde-Først-Søk (BFS) |
|---|---|---|
| Traverseringsrekkefølge | Utforsker så langt som mulig langs hver gren før den går tilbake | Utforsker alle nabo noder på gjeldende nivå før den går til neste nivå |
| Implementering | Rekursiv eller Iterativ (med stakk) | Iterativ (med kø) |
| Minnebruk | Generelt mindre minne (for dype trær) | Generelt mer minne (for brede trær) |
| Korteste vei | Ikke garantert ĂĄ finne den korteste veien | Garantert ĂĄ finne den korteste veien (i uvektede grafer) |
| Brukstilfeller | Stifinnelse, topologisk sortering, sykkeldeteksjon, løse labyrinter, parsing av uttrykk | Finne korteste vei, graf traversering, web crawling, finne nærmeste naboer, flomfylling |
| Risiko for uendelige løkker | Høyere risiko (krever nøye strukturering) | Lavere risiko (utforsker nivå for nivå) |
Velge Mellom DFS og BFS
Valget mellom DFS og BFS avhenger av det spesifikke problemet du prøver å løse og egenskapene til treet eller grafen du jobber med. Her er noen retningslinjer for å hjelpe deg med å velge:
- Bruk DFS nĂĄr:
- Treet er veldig dypt og du mistenker at løsningen er langt nede.
- Minnebruk er en stor bekymring, og treet ikke er for bredt.
- Du trenger ĂĄ oppdage sykluser i en graf.
- Bruk BFS nĂĄr:
- Du trenger ĂĄ finne den korteste veien i en uvektet graf.
- Du trenger å finne de nærmeste nodene til en start node.
- Minne ikke er en stor begrensning, og treet er bredt.
Utover Binære Trær: DFS og BFS i Grafer
Mens vi først og fremst har diskutert DFS og BFS i sammenheng med trær, er disse algoritmene like anvendelige på grafer, som er mer generelle datastrukturer der noder kan ha vilkårlige forbindelser. Kjerneprinsippene forblir de samme, men grafer kan introdusere sykluser, som krever ekstra oppmerksomhet for å unngå uendelige løkker.
Når du bruker DFS og BFS på grafer, er det vanlig å opprettholde et "besøkt" sett eller array for å holde styr på noder som allerede er utforsket. Dette forhindrer at algoritmen besøker noder på nytt og setter seg fast i sykluser.